from collections import namedtuple
from base.util import *
from Quicker.qk_other import *

if "globals":
    global_s = {}


    def globals_operate(operate, name, val=""):
        if operate == "get":
            res = global_s.get(name, "")
        elif operate == "set":
            if val != "":
                # 仅忽略空字符串, 因为如果直接用布尔判断, 会无法兼容布尔值
                empty = "《空》"
                val = "" if val == empty else val
                res = global_s[name] = val
            else:
                # 设置无, 则设置空字符串
                res = ""
        elif operate == "has":
            res = name in global_s
        elif operate == "get_all":
            res = global_s
        else:
            res = ""
        return dict(res=res, global_s=global_s)


class CodeExec:

    def __init__(self):
        self.code = ""

    def main(self, code=None, version=None, vars_q=None):
        """
        1.exec的具体用法, 见笔记
        2.不能使用参数名"__"
        """
        res = {"result": "",
               "err": ""}
        try:
            if version == "2":
                self.exec2_or_raise(code, vars_q, res)
            else:
                self.exec1(code, res)
        except (Exception,):
            self.make_raise(res, code)
        finally:
            return res

    def make_code(self, exp):  # 处理
        exp = exp.strip()
        if not exp: return "【代码为空】"
        if "把局部变量注入到全局变量":
            _code = """
locals2 = {k: v for k, v in locals().items() if k != "_new_var_names"}
repeat_vars = locals2.keys() & globals().keys()
if repeat_vars:
    raise Exception(f"存在重复的变量名"
                    f": {' '.join(repeat_vars)} "
                    f"(新建变量与Quicker变量/全局变量)")
_new_var_names.extend(locals2.keys())
globals().update(locals2)
"""
            exp = exp.replace("# vv", _code.strip())
        if "删除后方的注释,还有空行":
            li = drop_end(*(exp.splitlines(),
                            lambda x: x.startswith("#") or x.strip() == ""))
        if "处理最后一行":
            def del_print(s: str):
                s, n = re.subn(r"^(print|cc|pp\w+|pp\(\)|\w*test\w*)\(", "", s, 1)
                if n > 0:
                    s = re.sub(r"\)$", "", s, 1)
                return f"__ = {s}"

            li[-1] = del_print(li[-1])
        if "删除开头的 from, import":
            li = dropwhile(
                lambda x: re.search(r"^import|^from", x), li)
        self.code = "\n".join(li)
        return self.code

    def make_raise(self, res, code):
        """处理异常信息
        """
        # 返回异常信息, 并去掉第二第三行
        li = traceback.format_exc().splitlines(True)
        li[1:3] = []
        msg = "".join(li)

        # 处理异常信息中的行数, 如果超出原来的, 则返回原最长行数.
        def repl(m: Match):
            g1, g2, g3, g4 = m.groups()
            code_len = code.strip().count("\n")+1
            g3 = code_len if (int(g3) > code_len) else int(g3)+1
            return f"{g1}{g2}【{g3}】{g4}"

        msg = re.sub(r"(File \"<string>\", )(line )(\d+)(, in \w+?\n\w+?:)",
                     repl, msg, count=1)
        res["err"] = msg
        if "各种异常类型_测试用例":
            """
            各种异常类型
                重复    
                    quicker变量重复
                    自定义变量重复
                    自定义与Quicker重复
                异常
                    前面
                    后面
            """

    def exec2_or_raise(self, exp, vars_q, res):  # 判断+执行
        """code返回值只能是__"""
        if "vars_q 转字典":
            if isinstance(vars_q, str):
                vars_q = json.loads(vars_q)
        if "判断: 是否存在重复变量名":
            repeat_vars = vars_q.keys() & globals().keys()
            if repeat_vars:
                res["err"] = (f"存在重复的变量名"
                              f": {' '.join(repeat_vars)} "
                              f"(Quicker变量与全局变量重复)")
                return None
        if "执行":
            exp = self.make_code(exp)
            new_var_names = []  # 代码块内部的变量名, 用于后续删除
            __ = None
            globals().update(**vars_q, **{'null': None,
                                          'false': False, 'true': True})  # 注入变量
            err_li = []  # 因为这里的finally有些奇怪, 必须是可变类型才能访问
            try:
                def __func(_new_var_names):
                    exec(exp)
                    return locals()["__"]

                __ = __func(new_var_names)
            except (Exception,) as err1:
                err_li.append(err1)
            finally:
                for k in chain(vars_q.keys(), new_var_names): del globals()[k]  # 删除变量
                if err_li:
                    raise Exception() from err_li[0]
        if "判断: 是否存在循环引用":
            # __ = locals()["__"]  # 内外层变量不能相同
            if (__ is locals()) or (__ is globals()):
                res["err"] = ("不能把local()、vars()、globals()作为返回值. 因为会导致循环引用, "
                              "请使用 copy.deepcopy(locals()), 或者 vars_q")
            self.type_check(res, __)

    def exec1(self, __exp, __res):
        # 旧版
        exec(__exp)
        if "判断是否存在循环引用":
            __2 = locals()["__"]  # 内外层变量不能相同
            if __2 is locals() or __2 is globals():
                __res["err"] = ("不能把local()、vars()、globals()作为返回值. 因为会导致循环引用, "
                                "请使用copy.deepcopy(locals()), __vars_q")
                return None
            else:
                __res["result"] = __2

    def type_check(self, end_res, result):
        # 返回值必须是json支持的对象
        if end_res is None:
            result = ""
        elif callable(result):
            end_res["err"] = "不能把可调用对象作为返回值, 请修改最后一行"
            result = ""
        elif isinstance(result, (UserString, UserList, UserDict)):
            result = (str(result) if isinstance(result, UserString) else
                      list(result) if isinstance(result, UserList) else
                      dict(result))
        # 原始类型是user类型的父类, 必须放在后面
        elif isinstance(result, (str, int, list, tuple, dict, float)):
            pass
        else:
            end_res["err"] = f"返回值必须是json支持的对象, 而不是: {type(result)}"
        end_res["result"] = result


class keyCodeCheck:
    """按键检测: 对符"""

    def __init__(self, operate="pair_symbol", li_keyCode=None):
        self.pair_symbols = dict(oem7="\"\" ''".split(),
                                 oemopenbrackets="[] {}".split(),
                                 d9=["()"]*2)
        self.li_keyCode = li_keyCode or globals_operate("get", "keyCode_s")["res"]
        self.li2 = self.filter_keyCode()
        self.operate = operate

    def __call__(self, *args, **kwargs):
        if self.operate == "pair_symbol":
            res = self.get_pair_symbols()
            return res if res else ""  # None to ""

    def filter_keyCode(self):
        # 删除反引号, 对shift进行转换, 再去重
        set_, li2 = set(), []
        for e in self.li_keyCode:
            e = e.lower()
            if e == "oemtilde".lower():
                continue
            e = "shift" if ("shift" in e) else e
            # if e not in set_:
            #     set_.add(e)
            li2.append(e)
        uli.del_near_repeat(li2)
        return li2

    def get_pair_symbols(self):
        if "取最后两个值":
            li2, d = self.li2, self.pair_symbols
            li = d.keys()
            # 取最后两个
            if len(li2) >= 2:
                a, b = li2[-2:]
            else:
                return None
        if "判断多种情况, 并返回":
            # shift和对符.  判断并获取
            if (a in li and b == "shift" or
                    b in li and a == "shift"):
                df_key = a if (a in li) else b
                return d[df_key][1]
            # 对符1, 对符2
            if a in li and b in li:
                df_key = b
                return d[df_key][0]
            # 非对符, 对符2
            if a not in li and b in li:
                df_key = b
                return d[df_key][0]

    def _test_case(self):
        s = """
Oem7	shift	''
shift	Oem7	''
Oem7	D9	()
x	Oem7	""
x	y	空白
Oem7	d1	空白
        """


class QkTool:
    """
    文本处理或其他工具  (文本盒)
    去除"\r"的方式: quicker装饰器, _quicker类, ustr()
    """

    def __init__(self, *args):
        # 去除字符串中的所有"\r"
        self.args = qk_decorate().del_r(*args)
        self.ss = self.args[0] if len(args) > 0 else ""

    def each_arg_to_eval(self):
        """
        each(li, li[0].aa().bb()) -> each(li, "e.aa().bb()")
        """
        # s = r"each(li, li[0].aa().bb())"
        s = self.args[0]
        _ = re.sub(r"(, ?)(.+?\.)(.*)(\))", r'\1"e.\3"\4', s)
        return _

    def add_num_at_line_start(self):
        """
        给每行开头加上数字, 递增, 行开头不是空字符串.
        如果已经有数字,则替换
        :return
        1.aa
            aa
        2.bb
            bb
        """

        def repl(m: Match):
            nonlocal count
            count += 1
            indent = m.group(1)
            return f"{indent}{count}."

        s = self.args[0].strip("\n")
        count = 0
        _ = re.sub(r"^(\s*)(?:\d\.)?", repl, s, flags=re.M)
        return _

    def to_f_str(self):
        s = self.args[0]
        left, s, right = ustr(s).take_LeftRight()
        # 正则: 以"+"分隔, 除非有括号包裹. 括号前有引号的除外
        li = re.findall(r"(?<!['\"])\(.+?\)|[^+\n]+", s.replace(" ", ""))
        # 拼接: 逐个拼接内层. 顺便删除开头结尾的括号, 如果右括号的左边不是左括号
        #   如果是双引号则不拼接{ }, 也顺便去掉双引号
        f = lambda e: ("{"+re.sub(r'^\(|\)$', "", e)+"}"
                       if (not re.search(r'^".+"$', e)) else
                       re.sub(r'^"|"$', "", e))
        gen = (
            f(e)
            for e in li
        )
        # 补上右括号
        gen = (
            (e[0:-1]+")"+"}") if e.endswith("(}") else e
            for e in gen
        )
        # pp(exit_=1)(gen)
        # 拼接外层
        s = "f\""+"".join(gen)+"\""
        return left+s+right

    def to_class(self, cla_name="qkt(QkTool)"):
        """
        新建类,把多个函数放在类中,变成类中的方法
        """
        s = self.args[0]
        # 增加self
        s = re.sub(r"(?m)(^def \w+\()(.)", rf"\1self, \2", s)
        # 删除多余的", "
        s = re.sub(r"(\(self)(, )(\))", r"\1\3", s)
        # 增加缩进
        s = re.sub(r"(?m)^", r"    ", s)
        # 增加类名
        s = f"class {cla_name}:\n{s}"
        return s

    def for_loop_to_iter(self, ab="()"):
        """
        for循环  -> 推导式
         开发时间: 25*4
        """
        a, b = ab
        # 增加一行, 使得列表索引从1开始
        s = self.args[0]
        li_from_s = [""]+s.splitlines()
        if "yield " in s:
            e, li = re.findall(r"for (.+) in (.+):", li_from_s[1])[0]
            if len(li_from_s[2:]) == 1:
                if_ = ""
                e2 = re.findall(r"yield (.+)", li_from_s[2])[0]
            elif len(li_from_s[2:]) == 2:
                if_ = re.findall(r"if (.+):", li_from_s[2])[0]
                e2 = re.findall(r"yield (.+)", li_from_s[3])[0]
            else:
                return s
            li_new = "li" if not re.search(r"^\w+$", li) else li
            if not if_:
                return f"{li_new} = {a}{e2} for {e} in {li}{b}"
            else:
                return f"""{li_new} = {a}
    {e2}
    for {e} in {li}
    if {if_}
{b}"""
        else:
            li2 = re.findall(r"(\w+) = \[]", li_from_s[1])[0]
            e, li = re.findall(r"for (.+) in (.+):", li_from_s[2])[0]
            if len(li_from_s[3:]) == 1:
                if_ = ""
                e2 = re.findall(r"append\((.+)\)", li_from_s[3])[0]
            elif len(li_from_s[3:]) == 2:
                if_ = re.findall(r"if (.+):", li_from_s[3])[0]
                e2 = re.findall(r"append\((.+)\)", li_from_s[4])[0]
            else:
                return s
            if not if_:
                return f"{li2} = {a}{e2} for {e} in {li}{b}"
            else:
                return f"""{li2} = {a}
    {e2}
    for {e} in {li}
    if {if_}
{b}"""

    def del_space(self):
        """
        1.去除中英文之间的. "中文 英文" or "英文 中文"
        2.中文: 汉字与汉字符号
        :return:
        """
        s: str = self.args[0]
        s = re.sub(r"(?<=[^\x00-\xff]) +(?=[a-zA-Z])"
                   r"|"
                   r"(?<=[a-zA-Z]) +(?=[^\x00-\xff])", r"", s)
        return s

    def del_print(self):
        # 如果符合, 则需要对后面的多删除一次
        s = self.ss
        s, n = re.subn(r"str\(print\(", "", s, 1)
        if n == 1:
            s = ustr(s).rreplace(")", "", 2)
        else:
            print_li = r"""
            print\(
            cc\(
            pp\(.*?\)\(""".strip().splitlines()
            # pat = "|".join(each("e.strip()", print_li))
            pat = "|".join(map(str.strip, print_li))
            s = (ustr(s)
                 .sub(pat, r"", 1)
                 .rreplace(")", "", 1))
        return s

    def lines_to_regex(self, strip_=1):
        """多行字符串拼接成正则
        s: 如果是代码, 需要先转义再传入.
            如果是外部的文本则不需要
        """
        s = self.ss
        f = str.strip if strip_ else (lambda s: s)
        li = map(f, s.strip().splitlines())
        return "|".join(li)

    def format_attr(self):
        """ 属性格式化 (停用: 因为本来是用于each(), 但现在感觉用map就可以了)
        str.strip(s, "-")  ->  s.strip("-")
        Proc.get_port(p) -> p.get_port()
        """

        def repl(m: Match):
            attr1_left, args, right, attr2 = m.groups()
            self_, args = re.findall(r"(\w+)(?:, )?(.*)", args)[0]
            return f"{self_}{attr1_left}{args}{right}{attr2}"

        return re.sub(r"\w+"
                      r"(\.\w+\()(.*?)(\))"
                      r"(.*)", repl, s)

    def insert_var_to_func(self):
        """

        """
        s = self.ss
        _ = re.sub(
            r"""
            (.*def\ [^\n]+:\n)  # 函数名
            (\ +\"\"\".*?\"\"\"\n)?  # 注释
            ((?:\ {4})+)  # 缩进
            (.*)  # 其他
            \ {4}(\w+\ ?=\ ?.*)$  # 最后一行, 新的变量
            """,
            r"\1\2\3\5\n\3\4"
            , s, flags=re.VERBOSE | re.S)
        return _

    def lambda_to_func(self):
        """
        lambda to func
        """
        s = self.ss
        if re.search(r"def (\w+)\((.*?)\):", s):
            _ = re.sub(r"def (\w+)\((.*?)\):\n(?: {4})+return (.*)",
                       r"\1 = lambda \2: \3", s)
        else:
            _ = re.sub(r"( {4})(\w+) = lambda (.*?): (.*)",
                       r"def \2(\3):\n    \1return \4",
                       s)
        return _

    def num_to_cn(self):
        """

        d.get
        """
        li1 = list("123456789")+["10"]
        li2 = list("一二三四五六七八九十")
        d = dict(zip(li1, li2))

        repl = lambda m: d.get(m.group())
        _ = re.sub(r"10|\d", repl, self.ss)
        return _

    def Comment_or_not(self):
        """
        注释或反注释(非文本盒
        """
        s = self.ss
        if not s.strip():
            return "# "
        if re.search(r"(\s*)(# ?)(.+)$", s, re.M):
            _ = re.sub(r"(\s*)(# ?)(.+)$", r"\1\3", s, flags=re.M)
        else:
            _ = re.sub(r"(\s*)(.+)$", r"\1# \2", s, flags=re.M)
        return _

    def try_except(self):
        """

        """
        s = self.ss
        # s = ustr(s).del_indents()
        a, *b = s.split("\n\n")
        b = "".join(b)
        return f"""
try:
{a}
except (Exception,):
{b}
"""

    def t_replace(self, s=""):
        # 缩进换成\t
        s = s if s else self.ss
        self.ss = s.replace("    ", "\t")
        return self.ss

    def if_convert(self):
        """
        if if_:
            a += b
         ->
        a = a+b if if_ else a
        """
        s = ustr(self.t_replace()).indent_back()
        s = re.sub(r"if (.+):\n\t(\w+) ([+-])= (\w+)",
                   r"\2 = \2+\4 if \1 else \2", s)
        return s

    def maxLen_li(self, e, li: list, max_len=3) -> list:
        """定长列表: 超出限制时, 删除前面的
        """
        li.append(e)
        return list(deque(li, max_len))

    def intitle(self):
        # 匹配并拆分
        s = self.ss.strip()
        m = re.search(r"(.*?)( \d+)$", s)
        # 判断并添加后缀
        if m:
            # 再拆分
            a, b = m.groups()
            li1 = a.split()
            li2 = re.findall(r"\d", s)
            # 添加后缀
            try:
                for digit in li2:
                    i = int(digit)-1
                    li1[i] = f"{li1[i]}!"
                s = " ".join(li1)
            except (IndexError,):
                raise IndexError("索引越界: 请检查数字")
        # 替换: "!" -> "intitle"
        return re.sub(
            r"([^! ]+)!",  # 不等于"!"或空格的多个字符
            r"intitle:\1",
            s
        )

    def comple_item_split(self, sep1="|||", sep2="##"):
        """
        分隔「补全1」的条目
        """
        s = self.ss
        *key, s = s.split(sep1, 1)
        s, *tag = s.rsplit(sep2, 1)
        key, tag = map("".join, [key, tag])
        value = s
        tup1 = namedtuple("tup", "key value tag")
        return tup1._make([key, value, tag])

    def repl_actionId_and_others(self) -> dict:
        """
        aa -> {a1}
        bb -> {b2}
        """

        s = self.ss
        pat_actionId = r"[a-z\d]{8}-[a-z\d]{4}-[a-z\d]{4}-[a-z\d]{4}-[a-z\d]{12}"
        pat_url = r"(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]"
        cc = ReplRestore(name="动作id或url--")
        s = cc.repl(rf"(({pat_url})|({pat_actionId}))", s)
        return dict(s=s, li=cc.li)

    def restore_from_repl(self, li):
        s = self.ss
        s = ReplRestore(name="动作id或url--").restore(s, li)
        return s

    def TickTick(self, d_tag: dict = None, d_priority: dict = None):
        """滴答清单_快速添加
        替换并提取, 分别得到tag和优先级
            分别映射和拼接 tag和优先级
        """
        # pp()(d_tag)
        # pp()(d_priority)
        d_tag = dict(
            zip("qk py ak".split(),
                "quicker python anki".split())) if \
            (d_tag is None) else d_tag
        d_priority = dict(
            zip("123", "高优先级 中优先级 低优先级".split())) if \
            (d_priority is None) else d_priority
        s = self.ss

        # 前面只能是空白字符串, 或者没有字符
        pat1 = r"(?<!\S)"+"#"+ustr().join_to_pat(d_tag.keys())
        pat2 = r"(?<!\S)"+"!"+ustr().join_to_pat(d_priority.keys())

        s, tag_li = ustr(s).sub_take(pat1)
        s, priority_li = ustr(s).sub_take(pat2)

        tag_li = ["#"+d_tag.get(e, "") for e in tag_li]
        priority_li = ["!"+d_priority.get(e, "") for e in priority_li]

        line_0 = ustr(s).line(0)
        _ = dict(line_0=line_0, tag_li=list(tag_li),
                 priority_li=list(priority_li),
                 s=s.strip()+"\n")
        # pp()(_)
        return _

    def repl_s(self, old_s_and_new_s: str):
        """
        批量替换, 参数格式:
        a,b,c
        1,2,3
        """
        s = self.ss.strip()
        line_0, line_1 = old_s_and_new_s.strip().splitlines()
        old_s = [
            re.sub(r"\\(,)", r"\1", e)
            for e in re.split(r"(?<!\\),", line_0)
        ]
        new_s = [
            re.sub(r"\\(,)", r"\1", e)
            for e in re.split(r"(?<!\\),", line_1)
        ]
        # pp(exit_=1)(old_s, new_s)
        _ = ustr(s).replaceMoreToMore(old_s, new_s)
        return _


"""
测试/开发流程
1.test: func, class
2.重启服务器_测试端
3.Quicker文本盒
    任务类型: 复制并新建一个任务类型
    修改任务类型: 代码复制进去, 名称|数字, 并复制
    列表管理: 增加并粘贴任务类型
    """
if __name__ == '__main__':
    #     s = """
    # aa|||123##默认标签
    # bb|||b9fc3931-baa9-4a34-b4b7-4a8946bbdb90##默认标签
    # cc|||https://www.runoob.com/regexp/regexp-syntax.html#content##默认"""
    """有时候要重启两次才生效, 
    如果开了c#服务器的话"""
    s = """
aa
bb
    1.aa
    1.bb"""
    _ = QkTool(s).add_num_at_line_start()
    pp()(_)
